#include "editortabwidget.h"

#include <QMouseEvent>
#include <QTabBar>
#include <QMenu>
#include <QDebug>
#include <QToolButton>
#include <QApplication>
#include <QFileInfo>
#include <QMessageBox>
#include <QFileDialog>
#include <QFileSystemWatcher>

#include "meditor.h"
#include "mainwindow.h"
#include "mannasettings.h"
#include "misc.h"

EditorTabWidget::EditorTabWidget(QWidget *parent)
        : QTabWidget(parent)
{
    setMouseTracking(true);
    m_cross = new QToolButton(this);
    m_cross->hide();
    m_cross->setIcon( QIcon(":/images/file/closeall.png") );
    connect(m_cross, SIGNAL(clicked()), this, SLOT(slotCloseTab()) );
    m_cross->setGeometry(0,0,15,15);
    tabBar()->installEventFilter(this);

    m_clickedItem = -1;
    m_modifiedFiles = 0;

    m_fsWatcher = new QFileSystemWatcher(this);
    connect (m_fsWatcher, SIGNAL(fileChanged(const QString &)),
        this, SLOT(slotFileChanged(const QString &)));

    MannaSettings *ss = MannaSettings::getMannaSettings();
    ss->beginGroup("EditorSetting");
    m_zoomFactor = ss->value("ZoomFactor", 2).toInt();
    ss->endGroup();
    m_mainWindow = static_cast<MainWindow*> (parent);
}

bool EditorTabWidget::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == tabBar()) {
        if (event->type() == QEvent::HoverLeave) {
            QPoint point = m_cross->mapToGlobal( QPoint(0, 0) );
            QRect rect(point.x(), point.y(), m_cross->width(), m_cross->height());
            if ( !rect.contains( QCursor::pos() ) )
                m_cross->hide();
        } else if (event->type() == QEvent::HoverMove) {
            QHoverEvent *mouseEvent = static_cast<QHoverEvent *>(event);
            m_mousePos = mouseEvent->pos();
            for (int i=0; i<tabBar()->count(); i++) {
                if ( tabBar()->tabRect(i).contains( mouseEvent->pos() ) ) {
                    m_clickedItem= i;
                    break;
                }
            }
            m_cross->setGeometry(tabBar()->tabRect(m_clickedItem).x()+tabBar()->tabRect(m_clickedItem).width()-m_cross->width()-5,
                                5,
                                m_cross->width(),
                                m_cross->height()
                               );
            m_cross->show();
        } else if (event->type() == QEvent::MouseButtonRelease ) {
            qApp->restoreOverrideCursor();
        } else if (event->type() == QEvent::MouseButtonPress ) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
            for (int i=0; i<tabBar()->count(); i++) {
                if ( tabBar()->tabRect(i).contains( mouseEvent->pos() ) ) {
                    m_clickedItem = i;
                    break;
                }
            }
            if ( mouseEvent->button() == Qt::LeftButton )
                qApp->setOverrideCursor( Qt::OpenHandCursor );
            if ( mouseEvent->button() == Qt::RightButton ) {
                QMenu *menu = new QMenu(this);
                QFont font("Helvetica",9,QFont::Normal);
                menu->setFont(font);
                connect(menu->addAction(QIcon(":/images/file/close.png"),
                    tr("Close Tab")), SIGNAL(triggered()), this, SLOT(slotCloseTab()) );
                connect(menu->addAction(QIcon(":/images/file/closeother.png"),
                    tr("Close Other Tabs")), SIGNAL(triggered()), this, SLOT(slotCloseOtherTab()) );
                connect(menu->addAction(QIcon(":/images/file/closeall.png"),
                    tr("Close All Tabs")), SIGNAL(triggered()), this, SLOT(slotCloseAllTab()) );
                menu->addSeparator();
                connect(menu->addAction(tr("Set file options")),
                    SIGNAL(triggered()), this, SLOT(slotSetFileOption()) );

                menu->exec(mouseEvent->globalPos());
                delete menu;
            }
        } else if (event->type() == QEvent::MouseMove ) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
            for (int i=0; i<tabBar()->count(); i++) {
                if ( tabBar()->tabRect(i).contains( mouseEvent->pos() ) ) {
                    if ( swapTabs(i, m_clickedItem) ) {
                        setCurrentWidget(widget(i));
                        update();

                        int x;
                        if ( !tabBar()->tabRect(i).contains( mouseEvent->pos() ) ) {
                            if ( tabBar()->tabRect(m_clickedItem).x() < tabBar()->tabRect(i).x() ) {
                                x = tabBar()->tabRect(i).x();
                            } else {
                                x = tabBar()->tabRect(i).x()+(tabBar()->tabRect(i).width()
                                    -(qAbs(tabBar()->tabRect(i).width()-tabBar()->tabRect(m_clickedItem).width())));
                            }

                            QPoint point =  QPoint(x,mouseEvent->pos().y());
                            point =  widget(i)->mapToGlobal( point );
                            m_clickedItem = i;
                            QCursor::setPos ( point.x(), QCursor::pos().y() );
                        }
                        m_clickedItem = i;
                        break;
                    }
                }
            }
        }
    }
    return QTabWidget::eventFilter( obj, event);
}

bool EditorTabWidget::swapTabs(int index1, int index2)
{
    if (index1==index2)
        return false;
    int t1 = qMin(index1,index2);
    int t2 = qMax(index1,index2);

    index1=t1;
    index2=t2;

    QString name1 = tabBar()->tabText(index1);
    QString name2 = tabBar()->tabText(index2);

    QWidget *editor1 = widget(index1);
    QWidget *editor2 = widget(index2);

    removeTab(index2);
    removeTab(index1);

    insertTab(index1,editor2,name2);
    insertTab(index2,editor1,name1);
    return true;
}

void EditorTabWidget::slotCloseTab()
{
    if (doCloseTab(m_clickedItem)) {
        emit fileClosed(m_clickedItem);
    }
    //m_cross must hide after doCloseTab, otherwise m_cross will not hide.
    m_cross->hide();
}

void EditorTabWidget::slotCloseAllTab()
{
    int cnt = count();
    for (int i = cnt - 1; i >= 0; i--) {
        if (doCloseTab(i)) {
            emit fileClosed(i);
        }
    }
}

void EditorTabWidget::slotCloseOtherTab()
{
    int cnt = count();
    for (int i = cnt - 1; i >= 0; i--) {
        if (i == m_clickedItem)
            continue;
        if (doCloseTab(i)) {
            emit fileClosed(i);
        }
    }
}

void EditorTabWidget::closeCurrentTab()
{
    int cur = currentIndex();
    if (doCloseTab(cur)) {
        emit fileClosed(cur);
    }
    m_cross->hide();
}

void EditorTabWidget::closeAllTabs()
{
    int cnt = count();
    for (int i = cnt - 1; i >= 0; i--) {
        if (doCloseTab(i)) {
            emit fileClosed(i);
        }
    }
}

void EditorTabWidget::closeAllTabs(bool /*flag*/)
{
    int cnt = count();
    for (int i = cnt - 1; i >= 0; i--) {
        if (doCloseTab(i)) {
        }
    }
}

void EditorTabWidget::closeOtherTabs()
{
    int cnt = count();
    int cur = currentIndex();

    for (int i = cnt - 1; i >= 0; i--) {
        if (i == cur)
            continue;
        if (doCloseTab(i)) {
            emit fileClosed(i);
        }
    }
}

int EditorTabWidget::newFile()
{
    MEditor *ed = new MEditor(this);
    ed->setLineNumbersMarginEnabled(true);
    int lines = ed->lines();
    int len = QString::number(lines).length();
    ed->setLineNumbersMarginWidth(len);
    ed->setFolding(QsciScintilla::BoxedTreeFoldStyle);

    QString label = tr("Untitled");
    int retv = addTab(ed, label);
    connect (ed, SIGNAL(editorModified(MEditor*, bool)),
            this, SLOT(slotEditorModify(MEditor*, bool)));
    connect (ed, SIGNAL(copyAvailable(bool)),
            this, SIGNAL(copyAvailable(bool)));
    setCurrentIndex(retv);

    toZoom(m_zoomFactor);
    return retv;
}

int EditorTabWidget::openFile(const QString& file, bool reload/* = false */)
{
    int retv = -1;
    if (reload) {
        for (int i = 0; i < count(); i++) {
            MEditor *e = editor(i);
            QString filename = e->getFileName();
            if (filename == file) {
                retv = i;
                setCurrentIndex(i);
                /* Maintain m_modifiedFiles*/
                if (e->isModified()) {
                    slotEditorModify(e, false);
                }
                /* Discard change.*/
                e->closeFile(true);
                e->openFile(file);
                m_fsWatcher->addPath(file);
                return i;
            }
        }
    }
    /* Search the duplicated one.*/
    for (int i = 0; i < count(); i++) {
        QString filename = editor(i)->getFileName();
        if (filename == file) {
            retv = i;
            setCurrentIndex(i);
            break;
        }
    }
    if (retv < 0) {
        /* not found. */
        MEditor *ed = new MEditor(this);
        if (!ed->openFile(file)) {
            delete ed;
        } else {
            ed->setLineNumbersMarginEnabled(true);
            int lines = ed->lines();
            int len = QString::number(lines).length();
            ed->setLineNumbersMarginWidth(len);
            ed->setFolding(QsciScintilla::BoxedTreeFoldStyle);

            QString label = QFileInfo(file).fileName();
            retv = addTab(ed, label);
            connect (ed, SIGNAL(editorModified(MEditor*, bool)),
                    this, SLOT(slotEditorModify(MEditor*, bool)));
            connect (ed, SIGNAL(copyAvailable(bool)),
                    this, SIGNAL(copyAvailable(bool)));
            setCurrentIndex(retv);
            m_fsWatcher->addPath(file);
        }
    }
    toZoom(m_zoomFactor);
    return retv;
}

bool EditorTabWidget::doCloseTab(int index)
{
    MEditor *e = editor(index);
    if (!e)
        return false;

    m_fsWatcher->removePath(e->getFileName());

    if (!e->closeFile()) {
        /* User canceled */
        return false;
    }
    removeTab (index);
    delete e;
    return true;
}

void EditorTabWidget::slotEditorModify(MEditor *e, bool m)
{
    QString fname = e->getFileName();
    QString label;
    if (fname.isEmpty()) {
        label = tr("Untitled");
    } else {
        label = QFileInfo(fname).fileName();
    }

    int index = indexOf ((QWidget*)e);
    if (index < 0) {
        return;
    }
    if (m) {
        label = label + "*";
    }
    setTabText(index, label);

    if (m)
        m_modifiedFiles ++;
    else
        m_modifiedFiles --;
}

void EditorTabWidget::copy()
{
    MEditor *e = currentEditor();
    if (e)
        e->copy();
}

void EditorTabWidget::cut()
{
    MEditor *e = currentEditor();
    if (e)
        e->cut();
}

void EditorTabWidget::paste()
{
    MEditor *e = currentEditor();
    if (e)
        e->paste();
}

bool EditorTabWidget::save(int index)
{
    MEditor *e;
    if (index < 0)
        e = currentEditor();
    else
        e = editor(index);

    if (!e)
        return false;

    bool retv;
    if (e->isNewFile()) {
        retv = saveAs();
        if (retv) {
            m_fsWatcher->addPath(e->getFileName());
        }
    } else {
        m_fsWatcher->removePath(e->getFileName());
        retv = e->saveFile();
        m_fsWatcher->addPath(e->getFileName());
    }
    return retv;
}

bool EditorTabWidget::saveAs()
{
    MEditor *e = currentEditor();
    if (!e) return false;

    static QString dir;
    static QString selectedFilter = tr("All Files")+" (*)";
    MannaSettings *settings = MannaSettings::getMannaSettings();
    settings->beginGroup("LastestSaveDir");
    dir = settings->value( "LSDir",dir ).toString();
    QString fileName =
        QFileDialog::getSaveFileName(this,
                                    trUtf8("Save.."),
#ifdef    Q_OS_WIN
                                    dir.left(dir.lastIndexOf("/")+1),
#else
                                    dir.left(dir.lastIndexOf("/")+1)+"newfile",
#endif
                                    tr("C/C++ Sources")+" (*.cpp *.c *.h);;"+
                                    tr("Pascal Sources")+" (*.pas *.pp);;"+
                                    tr("Text")+" (*.txt *.TXT);;"+
                                    tr("All Files")+" (*)",
                                    &selectedFilter);
    if (fileName.isEmpty())
        return false;

    dir = QDir().absoluteFilePath(fileName);
    settings->setValue( "LSDir",dir );
    settings->endGroup();

    fileName = QDir::fromNativeSeparators(fileName);
    bool retv;
    retv = e->saveFile(fileName);
    if (retv) {
        m_fsWatcher->addPath(fileName);
        QString label = QFileInfo(fileName).fileName();
        setTabText(currentIndex(), label);
    }
    return retv;
}

bool EditorTabWidget::saveAll()
{
    int cnt = count();
    for (int i = cnt - 1; i >= 0; i--) {
        save(i);
    }
    return true;
}

void EditorTabWidget::selectAll(bool flag)
{
    MEditor *e = currentEditor();
    if (!e)
        return;

    e->selectAll(flag);
}

void EditorTabWidget::undo()
{
    MEditor *e = currentEditor();
    if (!e)
        return;

    e->undo();
}

void EditorTabWidget::redo()
{
    MEditor *e = currentEditor();
    if (!e)
        return;

    e->redo();
}

bool EditorTabWidget::hasFileModified()
{
    return m_modifiedFiles != 0;
}

bool EditorTabWidget::currentFileName(QString &fname)
{
    MEditor *e = currentEditor();
    if (!e)
        return false;

    fname = e->getFileName();
    return true;
}

bool EditorTabWidget::isCurrentModified()
{
    MEditor *e = currentEditor();
    if (!e)
        return false;
    return e->isModified();
}

bool EditorTabWidget::isCurrentNewFile()
{
    MEditor *e = currentEditor();
    if (!e)
        return false;
    return e->isNewFile();
}

void EditorTabWidget::invokeGotoLine()
{
    MEditor *e = currentEditor();
    if (!e)
        return;
    e->invokeGoToLine();
}

void EditorTabWidget::gotoLine(int line, bool toTop, bool highlight)
{
    MEditor *e = currentEditor();
    if (!e)
        return;
    if (highlight) {
        e->setSelection(line, 0, line, 1000);
    }
    e->gotoLine(line, toTop);
}

bool EditorTabWidget::gotoBrace()
{
    MEditor *e = currentEditor();
    if (!e)
        return false;
    return e->gotoBrace();
}

void EditorTabWidget::slotFileChanged(const QString & path)
{

    static int already_notified = 0;
    already_notified++;
    if (already_notified>1) { already_notified--;return;}
    int ret = QMessageBox::warning(this, tr("File has been modified outside..."),
                tr("The file %1 has been modified outside.\n"
                    "Do you want to reload it?")
                    .arg(path),
                QMessageBox::Yes | QMessageBox::No,
                QMessageBox::Yes);
    already_notified--;
    if (ret == QMessageBox::Yes) {
        openFile(path, true);
    } else {
        for (int i = 0; i < count(); i++) {
            MEditor *e = editor(i);
            QString filename = e->getFileName();
            if (filename == path) {
                if (!e->isModified()) {
                    e->setModified(true);
                }
                m_fsWatcher->addPath (filename);
                return;
            }
        }
    }
}

void EditorTabWidget::search(QString expr, bool re, bool cs, bool whole, bool forward)
{
    MEditor *e = currentEditor();
    if (!e) return;
    e->search(expr, re, cs, whole, forward);
}

void EditorTabWidget::searchNext()
{
    MEditor *e = currentEditor();
    if (!e) return;
    e->searchNext();
}

void EditorTabWidget::searchPrev()
{
    MEditor *e = currentEditor();
    if (!e) return;
    e->searchPrev();
}

void EditorTabWidget::replace(QString srcexpr,QString desexpr, bool re, bool cs, bool whole,bool replaceAll)
{
    MEditor *e = currentEditor();
    if (!e) return;
    e->replaceCur(srcexpr,desexpr,re,cs,whole,replaceAll);
}

void EditorTabWidget::setCurrentCaretLineVisible(bool v)
{
    MEditor *e = currentEditor();
    if (!e) return;
    e->setCaretLineVisible(v);
}

void EditorTabWidget::setAllCaretLineVisible(bool v)
{
    for (int i = 0; i < count(); i++)
        editor(i)->setCaretLineVisible(v);
}

void EditorTabWidget::setEditorProperties()
{
    for (int i = 0; i < count(); i++)
        editor(i)->setEditorProperties();
}

void EditorTabWidget::slotSetFileOption(bool byAction/* = false */)
{
    MEditor *e;
    if (byAction)
        e = currentEditor();
    else
        e = editor(m_clickedItem);
    if (!e)
        return;

    e->setFileOption();
}

QString EditorTabWidget::getCurrentCompileOption()
{
    QString opt;
    MEditor *e = currentEditor();
    if (!e)
        return opt;
    opt = e->getCompileOption();
    return opt;
}

QString EditorTabWidget::getExecOption(QString filename)
{
    QString opt;
    MEditor *e = editor(filename);
    if (!e)
        return opt;
    opt = e->getExecOption();
    return opt;
}

MEditor *EditorTabWidget::editor (
            const QString & filename, bool openWhenFailed /*= false*/)
{
    for (int i = 0; i < count (); i++)
    {
        MEditor * e = editor(i);
        //qDebug ("iterate %s", editor->getFileName ().toLatin1 ().constData ());

        if (e->getFileName () == filename)
            return e;
    }

    if (openWhenFailed)
    {
        //qDebug("EditorTabWidget::getEditor");
        int idx = openFile (filename, false);

        if (idx != -1)
            return editor(idx);
    }

    return 0;
}

MEditor *EditorTabWidget::editor(int i)
{
    return static_cast<MEditor *>(widget(i));
}

MEditor *EditorTabWidget::currentEditor()
{
    return static_cast<MEditor *>(currentWidget());
}

void EditorTabWidget::setAllTabsReadOnly(bool flag)
{
    for (int i = 0;i < count();++i) {
        MEditor *e = editor(i);
        e->setReadOnly(flag);
    }
}

void EditorTabWidget::slotAddVarWatch(QString var)
{
    m_mainWindow->slotaddVarWatch(var);
}

void EditorTabWidget::toZoom(int zoom)
{
    m_zoomFactor = zoom;
    int cnt = count();
    for (int i = cnt - 1; i >= 0; i--) {
        MEditor *e = editor(i);
        e->zoomTo(zoom);
        int lines = e->lines();
        int len = QString::number(lines).length();
        e->setLineNumbersMarginWidth(len);
    }
    MannaSettings *ss = MannaSettings::getMannaSettings();
    ss->beginGroup("EditorSetting");
    ss->setValue("ZoomFactor", m_zoomFactor);
    ss->endGroup();
}
